package lobby

import (
	"gddgame.cc/galaxy/asteroid"
	"gddgame.cc/galaxy/asteroid/cluster"
	"gddgame.cc/galaxy/utils"
	"gddgame.cc/galaxy/utils/serialize"

	"strings"
	"sync"
	"time"
)

// pvp以及pve的匹配模式不同
// pve提供难度选择（多选），pvp则根据自身难度进行对手匹配
// pvp在匹配时会考虑双方实力，胜率相当，mmr分值相当
// pve在匹配时会考虑尽可能胜利，略微增加难度（客户端根据积分确定可以参加的难度，用来区分）
// pvp天梯赛制，需要所有玩家都是天梯赛，根据机制判断升级赛，通过调整单人的mmr来进行升级赛制的匹配
// pve天梯赛制，需要所有玩家都是天梯赛，根据机制判断升级赛，通过调整单人的mmr来进行升级赛制的匹配

const (
	_ = iota
	Join
	Leave
	Interval
)

type Match interface {
	ID() string
	GetComputer(channel string) Computer
	Channel(users map[string]string, options map[string]interface{}) (string, []byte, error)
	Parse(channel string, payload []byte) map[string]interface{}
	Need(users map[string]string, options map[string]interface{}) bool
}

type Computer interface {
	Refresh(q *Queue) ([]*Client, map[string]interface{})
	Add(client *Client, index int) ([]*Client, map[string]interface{})
	Timeout() ([]*Client, map[string]interface{})
}

type Service struct {
	matchMap map[string]Match
	event    utils.Event
	pool     sync.Pool
}

type Client struct {
	Options map[string]interface{}
	Time    time.Time
	People  int
	Users   map[string]string
}

var (
	defaultService = NewLobbyService()
)

func init() {
}

func Default() *Service {
	return defaultService
}

func NewLobbyService() *Service {
	s := &Service{
		matchMap: make(map[string]Match),
		event:    utils.NewEvent(),
		pool:     sync.Pool{},
	}
	s.pool.New = func() interface{} {
		return &Client{}
	}
	return s
}
func (service *Service) ID() string {
	return "lobby"
}
func (service *Service) OnSuccess(callback func(container cluster.Container, users map[string]string, matchId string, option map[string]interface{})) {
	_ = service.event.On("success", callback)
}
func (service *Service) RegisterMatch(match Match) {
	service.matchMap[match.ID()] = match
}
func (service *Service) Init(node cluster.Node) {
	container := node.Container()
	token := node.Token()
	// todo pool
	i := strings.Index(token, "/")
	matchId := token[:i]
	channel := token[i+1:]
	node.Debugf("[ Lobby ] Channel:%s, match: %s, token: %s", matchId, channel, token)

	m := service.Get(matchId)
	q := NewQueue(node, m, channel)
	q.successCallback = func(users map[string]string, option map[string]interface{}) {
		_, _ = service.event.Fire("success", []interface{}{container, users, matchId, option})
	}
	node.SetInstance(q)
	node.SetConfig(cluster.ThreadSafe, true)
	ticker := time.NewTicker(time.Second * 1)
	go func() {
		ctx := node.Context()
		for {
			select {
			case <-ctx.Done():
				ticker.Stop()
				return
			case <-ticker.C:
				if node == nil {
					break
				}
				node.Direct(node.CurrentNode()).Send(Interval)
			}
		}
	}()
	node.On(Join, func(message asteroid.Message) {
		bag := serialize.GetBag()
		bag.SetList(message.List())
		users := bag.ReadInterface(0).(map[string]string)
		payload := bag.ReadBytes(1)
		bag.Release()
		node.Debugf("[ Lobby ] Join: %s,%s", users)
		// 如果用户已经进入队列，则忽略加入请求
		// 客户端需要在用户切换状态后进行退出操作
		for userId, _ := range users {
			if q.Has(userId) {
				return
			}
		}
		cc := service.pool.Get().(*Client)
		cc.Users = users
		cc.People = len(users)
		cc.Options = q.Match.Parse(q.channel, payload)
		l := q.Push(cc)
		if l == 0 {
			_ = node.Close()
		}

	})
	node.On(Leave, func(message asteroid.Message) {
		bag := serialize.GetBag()
		bag.SetList(message.List())
		userId := bag.ReadString(0)
		group := bag.ReadBool(1)
		bag.Release()
		node.Debugf("[ Lobby ] Leave: %s, %s", userId, group)
		l := q.Remove(userId, group)
		if l == 0 {
			_ = node.Close()
		}
	})
	node.On(Interval, func(message asteroid.Message) {
		l := q.Update()
		if l == 0 {
			_ = node.Close()
		}
	})
}

func (service *Service) InitClient(client *cluster.Client) {

}

func (service *Service) CloseInstance(node cluster.Node) {
	// todo pool
	//fmt.Println("Close instance match")
}

func (service *Service) CloseClient(client *cluster.Client) {

}

func (service *Service) Get(matchId string) Match {
	return service.matchMap[matchId]
}

func (service *Service) Join(center *cluster.HashCenter, matchId string, users map[string]string, channel string, payload []byte) (string, error) {
	realChannel := matchId + "/" + channel
	center.Emit(service.ID(), realChannel, Join, nil, users, payload)
	return realChannel, nil
}

func (service *Service) Leave(center *cluster.HashCenter, realChannel string, userId string, group bool) {
	center.Emit(service.ID(), realChannel, Leave, nil, userId, group)
}
